"""
模块内修改--------------------------------------------------
本模块是一个比较灵活的App特征计算模块，具体的:
可以通过设置self.type_list和self.type_list_depend_time组合的type列，并通过self.del_type_combination_name_list对type组合进行一些修改
可以通过设置self.time_window_name_list设置时间窗口形式，由于本模块不同子模块需要的时间窗口形式不同，所以设置了self.time_window_name_list_less和self.time_window_name_list_more两个不同的时间窗口形式
可以通过设置self.time_window_type来设置时间窗口使用install-time还是update-time来计算(若type_list中有依赖安装时间还是更新时间的type，将跟时间窗口保持一致)
可以通过设置不同子模块的特征是否记录到self.feature_dic和不同子模块的的时间窗口形式来决定该子模块的特征是否需要计算环比和环差
"""

"""
迁移到其他模块--------------------------------------------------
若想将该模块迁移到其他模块，需要做的改变主要有
1. self.type_list
2. self.type_is_depend_time
3. self.del_type_combination_name_list
4. 对应的type的打标函数(mark)
"""


import datetime
import json
import os
import re
import sys
from copy import deepcopy

import numpy as np

from feature_set.base_feature import BaseFeature, RequstData


class AppUnCompV1(BaseFeature):
    def __init__(self):
        super().__init__()
        ##记录特征的字典
        self.feature_dic = {}

        ##定位根目录
        self.root_dir = self.get_root_dir(os.path.abspath("."))
        self.conf_dir = os.path.join(self.root_dir, "feature_conf")

        ##时间窗口相关
        self.time_window_type = ["updt", "inst"]
        self.time_window_dic = {
            "all": sys.maxsize,
            "m6": 180,
            "m3": 90,
            "m1": 30,
            "d15": 15,
            "d7": 7,
            "d1": 1,
        }

        ##type组合相关
        self.type_list = [
            ("pre_inst", ["nonpreinst", "ispreinst", "all"]),
            ("hour_slot", ["beforemorning", "morning", "afternoon", "evening", "all"]),
            ("comp", ["isnotcomp", "iscomp", "all"]),
        ]
        self.type_is_depend_time = {"pre_inst": False, "comp": False, "hour_slot": True}
        self.del_type_combination_name_list = (
            self.combination(
                [
                    ["ispreinst", "all"],
                    ["beforemorning", "morning", "afternoon", "evening"],
                    ["isnotcomp", "iscomp", "all"],
                ]
            )
            + self.combination(
                [
                    ["nonpreinst"],
                    ["beforemorning", "morning", "afternoon", "evening", "all"],
                    ["isnotcomp", "all"],
                ]
            )
            + self.combination(
                [
                    ["ispreinst", "all"],
                    ["all"],
                    ["isnotcomp", "all"],
                ]
            )
        )

        ##根据是否做环比两类时间窗口
        self.time_window_name_list_less = [x + "_0" for x in self.time_window_dic]
        self.time_window_name_list_more = ["all_0"] + self.combination(
            [["d1", "d15", "d7", "m1", "m3", "m6"], ["0", "1"]]
        )

        ##各子模块特征是否做环比环差
        self.submodel_to_iscyc_dict = {
            "num": True,
            "seinst": False,
            "seupdt": False,
            "dayinst": False,
            "dayupdt": False,
            "top": False,
            "up2in": False,
        }
        self.submodel_to_time_window_list_less_or_more_dict = {
            k: self.time_window_name_list_more if v else self.time_window_name_list_less
            for k, v in self.submodel_to_iscyc_dict.items()
        }

        ##记录中间信息的字典
        self.time_window_save_data_dic = {
            prefix: {x: {} for x in self.time_window_name_list_more}
            for prefix in self.time_window_type
        }

        # 不同子模块对应计算函数
        self.function_map = {
            "num": self.compute_num_feature,
            "seinst": self.compute_seinst_feature,
            "dayinst": self.compute_dayinst_feature,
            "seupdt": self.compute_seupdt_feature,
            "dayupdt": self.compute_dayupdt_feature,
            "top": self.compute_top_feature,
            "up2in": self.compute_up2in_feature,
            "cycinst": self.compute_cycinst_feature,
            "cycupdt": self.compute_cycupdt_feature,
        }

        ##需要根据不同国家进行重写
        self.country_info = None
        self.app_name = None
        self.package_name = None
        self.inst_time_name = None
        self.updt_time_name = None
        self.pre_inst_name = None

    def load_conf():
        pass

    ##展开Request_data中的数据并对applist进行转化和清理
    def load_request(self, request_data):
        self.country_info["feature_name"] = set(self.country_info["feature_name"])
        apply_time = self.trans_str_to_time(request_data.apply_time)
        self.apply_date = apply_time.date()

        try:
            app_list = []
            applist_data = request_data.data_sources["applist_data"]
            app_list = (
                json.loads(applist_data) if type(applist_data) == str else applist_data
            )
        except:
            pass

        if len(app_list) == 0 or type(app_list) != list:
            assert (
                1 == 0
            ), "传入的app_json无法解析或者解析后异常(不是list，或者为空list)"

        name_trans_hash = {
            "app": self.app_name,
            "package": self.package_name,
            "inst_time": self.inst_time_name,
            "updt_time": self.updt_time_name,
            "pre_inst": self.pre_inst_name,
        }

        clean_app_list = []
        for app in app_list:
            trans_app = {}
            trans_app["app"] = app[name_trans_hash["app"]]
            trans_app["package"] = app[name_trans_hash["package"]]
            trans_app["inst_time"] = datetime.datetime.utcfromtimestamp(
                int(app[name_trans_hash["inst_time"]]) // 1000
            ) + datetime.timedelta(hours=self.country_info["time_zone"])
            trans_app["inst_date"] = trans_app["inst_time"].date()
            trans_app["updt_time"] = datetime.datetime.utcfromtimestamp(
                int(app[name_trans_hash["updt_time"]]) // 1000
            ) + datetime.timedelta(hours=self.country_info["time_zone"])
            trans_app["updt_date"] = trans_app["updt_time"].date()
            trans_app["up2in_time_diff_days"] = (
                trans_app["updt_date"] - trans_app["inst_date"]
            ).days
            trans_app["up2in_time_diff_hours"] = (
                trans_app["updt_time"] - trans_app["inst_time"]
            ).total_seconds() // 3600
            trans_app["pre_inst"] = app[name_trans_hash["pre_inst"]]
            trans_app["inst_tfdays"] = (self.apply_date - trans_app["inst_date"]).days
            trans_app["updt_tfdays"] = (self.apply_date - trans_app["updt_date"]).days
            if trans_app["inst_time"] <= apply_time:
                clean_app_list.append(deepcopy(trans_app))
        self.app_list = clean_app_list
        self.get_type_combination_name()
        self.mark_tag()
        self.cal_time_window_save_data_dic()

    def gen_features(self, data: RequstData):
        try:
            self.load_request(data)
        except AssertionError as e:
            return {k: -999 for k in self.country_info["feature_name"]}

        rs_dict = {}
        for k, func in self.function_map.items():
            rs_dict.update(
                {
                    k + "_" + s: t if t != None else -999
                    for s, t in func().items()
                    if k + "_" + s in self.country_info["feature_name"]
                }
            )

        for k, v in rs_dict.items():
            if rs_dict[k]:
                rs_dict[k] = round(rs_dict[k], 4)
            else:
                continue
        return rs_dict

    ##标记各种需要的tag
    def mark_tag(self):
        self.cal_pre_inst_time_boundary()
        self.mark_pre_inst_tag()
        self.mark_comp_tag()
        self.mark_hour_slot_tag("inst")
        self.mark_hour_slot_tag("updt")
        self.mark_time_window_tag("inst")
        self.mark_time_window_tag("updt")
        self.mark_type_values()

    ##计算计数特征的函数
    def compute_num_feature(self):
        feature_dic = {}
        for prefix in self.time_window_type:
            for time_window_name in self.submodel_to_time_window_list_less_or_more_dict[
                "num"
            ]:
                type_cnt_all = [len(x[1]) - 1 for x in self.type_list]
                app_cnt_all = len(self.app_list)
                for i in range(len(self.type_list)):
                    type_cnt = len(
                        self.time_window_save_data_dic[prefix][time_window_name][
                            "type_cnt_dict"
                        ][i]
                    )
                    feature_dic[
                        self.gen_feature_name(
                            [
                                prefix,
                                self.type_list[i][0],
                                "type",
                                "cnt",
                                time_window_name,
                            ]
                        )
                    ] = type_cnt
                    feature_dic[
                        self.gen_feature_name(
                            [
                                prefix,
                                self.type_list[i][0],
                                "type",
                                "ratio",
                                time_window_name,
                            ]
                        )
                    ] = self.cal_two_division(type_cnt, type_cnt_all[i])

                for combination in self.combination_type_name_list:
                    cnt = self.time_window_save_data_dic[prefix][time_window_name][
                        "combination_cnt_dict"
                    ][combination]
                    feature_dic[
                        self.gen_feature_name(
                            [prefix, combination, "cnt", time_window_name]
                        )
                    ] = cnt
                    feature_dic[
                        self.gen_feature_name(
                            [prefix, combination, "ratio", time_window_name]
                        )
                    ] = self.cal_two_division(cnt, app_cnt_all)
        self.feature_dic.update(feature_dic)
        return feature_dic

    ##计算top特征的函数
    def compute_top_feature(self):
        feature_dic = {}
        for prefix in self.time_window_type:
            for time_window_name in self.submodel_to_time_window_list_less_or_more_dict[
                "top"
            ]:
                for i in range(len(self.type_list)):
                    max_index, max_value = self.cal_max_value(
                        self.time_window_save_data_dic[prefix][time_window_name][
                            "type_cnt_dict"
                        ][i]
                    )
                    feature_dic[
                        self.gen_feature_name(
                            [
                                prefix,
                                self.type_list[i][0],
                                "max",
                                "type",
                                time_window_name,
                            ]
                        )
                    ] = max_index
                    feature_dic[
                        self.gen_feature_name(
                            [
                                prefix,
                                self.type_list[i][0],
                                "max",
                                "type",
                                "cnt",
                                time_window_name,
                            ]
                        )
                    ] = max_value
                    feature_dic[
                        self.gen_feature_name(
                            [
                                prefix,
                                self.type_list[i][0],
                                "max",
                                "type",
                                "ratio",
                                time_window_name,
                            ]
                        )
                    ] = self.cal_two_division(max_value, len(self.app_list))
        self.feature_dic.update(feature_dic)
        return feature_dic

    ##计算最大连续相关特征的函数
    def compute_part_day_feature(self, prefix_list, time_window_less_or_more):
        feature_dic = {}
        for prefix in self.time_window_type:
            for time_window_name in time_window_less_or_more:
                for combination in self.combination_type_name_list:
                    day_cnt_dict = self.time_window_save_data_dic[prefix][
                        time_window_name
                    ]["day_cnt_dict"][combination][prefix_list[3]]
                    max_continus_info = self.time_window_save_data_dic[prefix][
                        time_window_name
                    ]["max_continus_info"][combination][prefix_list[3]]
                    max_good_start_day = max_continus_info["max_good_start_day"]
                    max_good_end_day = max_continus_info["max_good_end_day"]
                    tfdays_set = self.time_window_save_data_dic[prefix][
                        time_window_name
                    ]["tfdays_set"][combination][prefix_list[3]]
                    max_min_avg_std_feature_dic = {
                        prefix_list[0]: self.cal_max_min_avg_std(day_cnt_dict.values()),
                        prefix_list[1]: self.cal_max_min_avg_std(
                            max_continus_info["good_list"]
                        ),
                        prefix_list[2]: self.cal_max_min_avg_std(
                            max_continus_info["bad_list"]
                        ),
                    }
                    for prefix_ in prefix_list[:3]:
                        feature_dic[
                            self.gen_feature_name(
                                [prefix, combination, prefix_, "max", time_window_name]
                            )
                        ] = max_min_avg_std_feature_dic[prefix_][0]
                        feature_dic[
                            self.gen_feature_name(
                                [prefix, combination, prefix_, "min", time_window_name]
                            )
                        ] = max_min_avg_std_feature_dic[prefix_][1]
                        feature_dic[
                            self.gen_feature_name(
                                [prefix, combination, prefix_, "avg", time_window_name]
                            )
                        ] = max_min_avg_std_feature_dic[prefix_][2]
                        feature_dic[
                            self.gen_feature_name(
                                [prefix, combination, prefix_, "std", time_window_name]
                            )
                        ] = max_min_avg_std_feature_dic[prefix_][3]

                    feature_dic[
                        self.gen_feature_name(
                            [
                                prefix,
                                combination,
                                prefix_list[3],
                                "density",
                                time_window_name,
                            ]
                        )
                    ] = self.cal_density(list(tfdays_set))
                    feature_dic[
                        self.gen_feature_name(
                            [
                                prefix,
                                combination,
                                prefix_list[3],
                                "day",
                                "cnt",
                                time_window_name,
                            ]
                        )
                    ] = len(day_cnt_dict)
                    feature_dic[
                        self.gen_feature_name(
                            [
                                prefix,
                                combination,
                                prefix_list[4],
                                "cnt",
                                time_window_name,
                            ]
                        )
                    ] = sum(
                        [
                            v
                            for k, v in day_cnt_dict.items()
                            if max_good_start_day <= k <= max_good_end_day
                        ]
                    )
                    feature_dic[
                        self.gen_feature_name(
                            [
                                prefix,
                                combination,
                                "apply",
                                prefix_list[4],
                                "end",
                                "diff",
                                time_window_name,
                            ]
                        )
                    ] = self.cal_two_date_diff(self.apply_date, max_good_end_day)
                    cnt_max_day, _ = self.cal_max_value(day_cnt_dict)
                    feature_dic[
                        self.gen_feature_name(
                            [
                                prefix,
                                combination,
                                "apply",
                                prefix_list[3],
                                "cnt",
                                "max",
                                "diff",
                                time_window_name,
                            ]
                        )
                    ] = self.cal_two_date_diff(self.apply_date, cnt_max_day)
        return feature_dic

    ##计算安装最大连续相关特征的函数
    def compute_dayinst_feature(self):
        prefix_list = [
            "inst_everyday_cnt",
            "inst_good_continus_day",
            "inst_bad_continus_day",
            "inst",
            "inst_good_continus",
        ]
        feature_dic = self.compute_part_day_feature(
            prefix_list, self.submodel_to_time_window_list_less_or_more_dict["dayinst"]
        )
        self.feature_dic.update(feature_dic)
        return feature_dic

    ##计算更新最大连续相关特征的函数
    def compute_dayupdt_feature(self):
        prefix_list = [
            "updt_everyday_cnt",
            "updt_good_continus_day",
            "updt_bad_continus_day",
            "updt",
            "updt_good_continus",
        ]
        feature_dic = self.compute_part_day_feature(
            prefix_list, self.submodel_to_time_window_list_less_or_more_dict["dayupdt"]
        )
        self.feature_dic.update(feature_dic)
        return feature_dic

    ##计算start,end日期差特征的主函数
    def compute_part_se_feature(self, prefix_list, time_window_less_or_more):
        feature_dic = {}
        for prefix in self.time_window_type:
            for time_window_name in time_window_less_or_more:
                for combination in self.combination_type_name_list:
                    delmaxnull_info = self.time_window_save_data_dic[prefix][
                        time_window_name
                    ]["delmaxnull_info"][combination][prefix_list[0]]
                    for prefix_ in prefix_list:
                        prefix__ = prefix_.replace(prefix_list[0], "")
                        start_date = delmaxnull_info[
                            self.gen_feature_name([prefix__, "start_date"])
                        ]
                        end_date = delmaxnull_info[
                            self.gen_feature_name([prefix__, "end_date"])
                        ]
                        cnt = delmaxnull_info[self.gen_feature_name([prefix__, "cnt"])]

                        end_start_date_diff = self.cal_two_date_diff(
                            end_date, start_date
                        )
                        feature_dic[
                            self.gen_feature_name(
                                [
                                    prefix,
                                    combination,
                                    prefix_,
                                    "end",
                                    "start",
                                    "diff",
                                    time_window_name,
                                ]
                            )
                        ] = end_start_date_diff
                        feature_dic[
                            self.gen_feature_name(
                                [
                                    prefix,
                                    combination,
                                    prefix_,
                                    "apply",
                                    "end",
                                    "diff",
                                    time_window_name,
                                ]
                            )
                        ] = self.cal_two_date_diff(self.apply_date, end_date)
                        feature_dic[
                            self.gen_feature_name(
                                [
                                    prefix,
                                    combination,
                                    prefix_,
                                    "apply",
                                    "start",
                                    "diff",
                                    time_window_name,
                                ]
                            )
                        ] = self.cal_two_date_diff(self.apply_date, start_date)
                        feature_dic[
                            self.gen_feature_name(
                                [
                                    prefix,
                                    combination,
                                    prefix_,
                                    "day",
                                    "avg",
                                    "cnt",
                                    time_window_name,
                                ]
                            )
                        ] = self.cal_two_division(
                            cnt,
                            (
                                1 + end_start_date_diff
                                if end_start_date_diff != None
                                else None
                            ),
                        )
        return feature_dic

    ##计算安装start,end日期差特征的函数
    def compute_seinst_feature(self):
        prefix_list = ["inst", "instdelmaxnull30", "instdelmaxnull60"]
        feature_dic = self.compute_part_se_feature(
            prefix_list, self.submodel_to_time_window_list_less_or_more_dict["dayinst"]
        )
        self.feature_dic.update(feature_dic)
        return feature_dic

    ##计算更新start,end日期差特征的函数
    def compute_seupdt_feature(self):
        prefix_list = ["updt", "updtdelmaxnull30", "updtdelmaxnull60"]
        feature_dic = self.compute_part_se_feature(
            prefix_list, self.submodel_to_time_window_list_less_or_more_dict["dayupdt"]
        )
        self.feature_dic.update(feature_dic)
        return feature_dic

    ##计算更新和安装时间差的函数
    def compute_up2in_feature(self):
        feature_dic = {}
        for prefix in self.time_window_type:
            for time_window_name in self.submodel_to_time_window_list_less_or_more_dict[
                "up2in"
            ]:
                for combination in self.combination_type_name_list:
                    for prefix_ in ["days", "hours"]:
                        time_diff_list = self.time_window_save_data_dic[prefix][
                            time_window_name
                        ]["time_diff_list"][combination][prefix_]
                        td_max, td_min, td_avg, td_std = self.cal_max_min_avg_std(
                            time_diff_list
                        )
                        td_medium, td_percent90 = self.cal_quantile(time_diff_list)
                        feature_dic[
                            self.gen_feature_name(
                                [
                                    prefix,
                                    combination,
                                    "up2in",
                                    "time_diff",
                                    prefix_,
                                    "max",
                                    time_window_name,
                                ]
                            )
                        ] = td_max
                        feature_dic[
                            self.gen_feature_name(
                                [
                                    prefix,
                                    combination,
                                    "up2in",
                                    "time_diff",
                                    prefix_,
                                    "min",
                                    time_window_name,
                                ]
                            )
                        ] = td_min
                        feature_dic[
                            self.gen_feature_name(
                                [
                                    prefix,
                                    combination,
                                    "up2in",
                                    "time_diff",
                                    prefix_,
                                    "avg",
                                    time_window_name,
                                ]
                            )
                        ] = td_avg
                        feature_dic[
                            self.gen_feature_name(
                                [
                                    prefix,
                                    combination,
                                    "up2in",
                                    "time_diff",
                                    prefix_,
                                    "std",
                                    time_window_name,
                                ]
                            )
                        ] = td_std
                        feature_dic[
                            self.gen_feature_name(
                                [
                                    prefix,
                                    combination,
                                    "up2in",
                                    "time_diff",
                                    prefix_,
                                    "medium",
                                    time_window_name,
                                ]
                            )
                        ] = td_medium
                        feature_dic[
                            self.gen_feature_name(
                                [
                                    prefix,
                                    combination,
                                    "up2in",
                                    "time_diff",
                                    prefix_,
                                    "percent90",
                                    time_window_name,
                                ]
                            )
                        ] = td_percent90
                    ##更新安装在同一个小时认为是没有更新的app
                    beupdated_cnt = self.cal_nozero_num(time_diff_list)
                    feature_dic[
                        self.gen_feature_name(
                            [prefix, combination, "beupdted", "cnt", time_window_name]
                        )
                    ] = beupdated_cnt
                    feature_dic[
                        self.gen_feature_name(
                            [prefix, combination, "beupdted", "ratio", time_window_name]
                        )
                    ] = self.cal_two_division(beupdated_cnt, len(self.app_list))
        self.feature_dic.update(feature_dic)
        return feature_dic

    ##计算环比环差特征的主函数
    def compute_part_cyc_feature(self, prefix):
        feature_dic = {}
        for feature_name, fea2 in self.feature_dic.items():
            if feature_name.startswith(prefix) and feature_name.endswith("1"):
                fea1 = self.feature_dic[self.pop_append(feature_name, "0")]
                feature_dic[self.pop_append(feature_name, "diff")] = (
                    self.cal_two_difference(fea1, fea2)
                )
                feature_dic[self.pop_append(feature_name, "ratio")] = (
                    self.cal_two_division(fea1, fea2)
                )
        return feature_dic

    ##计算安装环比环差的函数
    def compute_cycinst_feature(self):
        feature_dic = self.compute_part_cyc_feature("inst")
        return feature_dic

    ##计算更新环比环差的函数
    def compute_cycupdt_feature(self):
        feature_dic = self.compute_part_cyc_feature("updt")
        return feature_dic

    ####下面是一些标记tag用到的函数

    ##计算预装时间边界
    def cal_pre_inst_time_boundary(self):
        inst_time_cnt_dic = {}
        max_cnt_inst_time = None
        max_cnt = 0
        for app in self.app_list:
            inst_time = app["inst_time"]
            if inst_time not in inst_time_cnt_dic:
                inst_time_cnt_dic[inst_time] = 0
            inst_time_cnt_dic[inst_time] += 1
        for inst_time, cnt in inst_time_cnt_dic.items():
            if cnt > max_cnt or (cnt == max_cnt and inst_time > max_cnt_inst_time):
                max_cnt_inst_time, max_cnt = inst_time, cnt
        self.pre_inst_time_boundary = max_cnt_inst_time

    ##标记是否预装的tag
    def mark_pre_inst_tag(self):
        for app in self.app_list:
            if (
                str(app["pre_inst"]) == "1"
                or app["inst_time"] <= self.pre_inst_time_boundary
            ):
                app["pre_inst_tag"] = 1
            else:
                app["pre_inst_tag"] = 0

    ##标记是否是竞品的tag
    def mark_comp_tag(self):
        for app in self.app_list:
            app["comp_tag"] = 1 if app["package"] in self.comp_set else 0

    ##标记时间段的tag
    def mark_hour_slot_tag(self, prefix):
        for app in self.app_list:
            hour = app[f"{prefix}_time"].hour
            if 0 < hour <= 6:
                app[f"{prefix}_hour_slot_tag"] = 0
            elif 6 < hour <= 12:
                app[f"{prefix}_hour_slot_tag"] = 1
            elif 12 < hour <= 18:
                app[f"{prefix}_hour_slot_tag"] = 2
            else:
                app[f"{prefix}_hour_slot_tag"] = 3

    ##标记时间窗口的tag
    def mark_time_window_tag(self, prefix):
        for app in self.app_list:
            tmp_time_window_tag = {
                time_window: (
                    0
                    if 0 <= app[f"{prefix}_tfdays"] <= days
                    else (1 if days < app[f"{prefix}_tfdays"] <= 2 * days else 2)
                )
                for time_window, days in self.time_window_dic.items()
            }
            app[f"{prefix}_time_window_tag"] = {
                f"{k}_{str(v)}" for k, v in tmp_time_window_tag.items()
            }

    ##标记各个type对应的值
    def mark_type_values(self):
        if sum(self.type_is_depend_time.values()) != 0:
            for app in self.app_list:
                type_value, type_name, combination_list = {}, {}, {}
                for prefix in ["inst", "updt"]:
                    type_value[prefix] = [
                        (
                            app[f"{x[0]}_tag"]
                            if not self.type_is_depend_time[x[0]]
                            else app[f"{prefix}_{x[0]}_tag"]
                        )
                        for x in self.type_list
                    ]
                    type_name[prefix] = [
                        self.type_list[i][1][type_value[prefix][i]]
                        for i in range(len(self.type_list))
                    ]
                    combination_list[prefix] = [
                        xx
                        for xx in self.combination(
                            [[x, "all"] for x in type_name[prefix]]
                        )
                        if xx in self.combination_type_name_list
                    ]
                app["type_value"], app["type_name"], app["combination_list"] = (
                    type_value,
                    type_name,
                    combination_list,
                )
        else:
            for app in self.app_list:
                type_value = [app[f"{x[0]}_tag"] for x in self.type_list]
                type_name = [
                    self.type_list[i][1][type_value[i]]
                    for i in range(len(self.type_list))
                ]
                combination_list = [
                    xx
                    for xx in self.combination([[x, "all"] for x in type_name])
                    if xx in self.combination_type_name_list
                ]
                app["type_value"], app["type_name"], app["combination_list"] = (
                    type_value,
                    type_name,
                    combination_list,
                )

    ####下面是一个计算各种type组合形式的函数

    ##这是计算各种type组合形式的函数
    def get_type_combination_name(self):
        self.combination_type_name_list = [
            x
            for x in self.combination([x[1] for x in self.type_list])
            if x not in self.del_type_combination_name_list
        ]

    ####下面是计算time_window_save_dic(计算特征所要存储的一些东西)的函数

    ##计算time_window_save_dic的主函数,包含初始化和计算两部分
    def cal_time_window_save_data_dic(self):
        for prefix in self.time_window_type:
            for time_window_name in self.time_window_name_list_more:

                self.time_window_save_data_dic[prefix][time_window_name][
                    "day_cnt_dict"
                ] = {
                    k: {x: {} for x in ["inst", "updt"]}
                    for k in self.combination_type_name_list
                }
                self.time_window_save_data_dic[prefix][time_window_name][
                    "time_diff_list"
                ] = {
                    k: {x: [] for x in ["days", "hours"]}
                    for k in self.combination_type_name_list
                }

                self.time_window_save_data_dic[prefix][time_window_name][
                    "tfdays_set"
                ] = {
                    k: {x: set() for x in ["inst", "updt"]}
                    for k in self.combination_type_name_list
                }
                self.time_window_save_data_dic[prefix][time_window_name][
                    "max_continus_info"
                ] = {
                    k: {x: {} for x in ["inst", "updt"]}
                    for k in self.combination_type_name_list
                }
                self.time_window_save_data_dic[prefix][time_window_name][
                    "delmaxnull_info"
                ] = {
                    k: {x: {} for x in ["inst", "updt"]}
                    for k in self.combination_type_name_list
                }
                self.time_window_save_data_dic[prefix][time_window_name][
                    "combination_cnt_dict"
                ] = {k: 0 for k in self.combination_type_name_list}
                self.time_window_save_data_dic[prefix][time_window_name][
                    "type_cnt_dict"
                ] = [{} for i in range(len(self.type_list))]
        for prefix in self.time_window_type:
            for app in self.app_list:
                type_value = (
                    app["type_value"]
                    if sum(self.type_is_depend_time.values()) == 0
                    else app["type_value"][prefix]
                )
                time_window = [
                    x
                    for x in app[f"{prefix}_time_window_tag"]
                    if x in self.time_window_name_list_more
                ]
                combination_list = (
                    app["combination_list"]
                    if sum(self.type_is_depend_time.values()) == 0
                    else app["combination_list"][prefix]
                )
                up2in_time_diff_day = app["up2in_time_diff_days"]
                up2in_time_diff_hour = app["up2in_time_diff_hours"]
                inst_date = app["inst_date"]
                updt_date = app["updt_date"]
                inst_tfdays = app["inst_tfdays"]
                updt_tfdays = app["updt_tfdays"]

                for time_window_name in time_window:
                    for combination in combination_list:
                        if (
                            inst_date
                            not in self.time_window_save_data_dic[prefix][
                                time_window_name
                            ]["day_cnt_dict"][combination]["inst"]
                        ):
                            self.time_window_save_data_dic[prefix][time_window_name][
                                "day_cnt_dict"
                            ][combination]["inst"][inst_date] = 0
                        self.time_window_save_data_dic[prefix][time_window_name][
                            "day_cnt_dict"
                        ][combination]["inst"][inst_date] += 1
                        if (
                            updt_date
                            not in self.time_window_save_data_dic[prefix][
                                time_window_name
                            ]["day_cnt_dict"][combination]["updt"]
                        ):
                            self.time_window_save_data_dic[prefix][time_window_name][
                                "day_cnt_dict"
                            ][combination]["updt"][updt_date] = 0
                        self.time_window_save_data_dic[prefix][time_window_name][
                            "day_cnt_dict"
                        ][combination]["updt"][updt_date] += 1

                        self.time_window_save_data_dic[prefix][time_window_name][
                            "time_diff_list"
                        ][combination]["days"].append(up2in_time_diff_day)
                        self.time_window_save_data_dic[prefix][time_window_name][
                            "time_diff_list"
                        ][combination]["hours"].append(up2in_time_diff_hour)
                        self.time_window_save_data_dic[prefix][time_window_name][
                            "combination_cnt_dict"
                        ][combination] += 1
                        self.time_window_save_data_dic[prefix][time_window_name][
                            "tfdays_set"
                        ][combination]["inst"].add(inst_tfdays)
                        self.time_window_save_data_dic[prefix][time_window_name][
                            "tfdays_set"
                        ][combination]["updt"].add(updt_tfdays)

                    for i in range(len(self.type_list)):
                        if (
                            type_value[i]
                            not in self.time_window_save_data_dic[prefix][
                                time_window_name
                            ]["type_cnt_dict"][i]
                        ):
                            self.time_window_save_data_dic[prefix][time_window_name][
                                "type_cnt_dict"
                            ][i][type_value[i]] = 0
                        self.time_window_save_data_dic[prefix][time_window_name][
                            "type_cnt_dict"
                        ][i][type_value[i]] += 1

            for time_window_name in self.time_window_name_list_more:
                for combination in self.combination_type_name_list:
                    day_cnt_dict = self.time_window_save_data_dic[prefix][
                        time_window_name
                    ]["day_cnt_dict"][combination]
                    max_continus_info = {
                        k: self.cal_max_continus(v) for k, v in day_cnt_dict.items()
                    }
                    self.time_window_save_data_dic[prefix][time_window_name][
                        "max_continus_info"
                    ][combination] = deepcopy(max_continus_info)

                    self.time_window_save_data_dic[prefix][time_window_name][
                        "delmaxnull_info"
                    ][combination] = {
                        k: self.cal_delmaxnull_info(day_cnt_dict[k], v)
                        for k, v in max_continus_info.items()
                    }

    ##这个函数用来计se模块特征所需要的一些信息
    def cal_delmaxnull_info(self, day_cnt_dict, max_continus_info):
        max_bad = max_continus_info["max_bad"]
        if max_bad == None:
            return {
                "start_date": None,
                "end_date": None,
                "cnt": None,
                "delmaxnull30_start_date": None,
                "delmaxnull30_end_date": None,
                "delmaxnull30_cnt": None,
                "delmaxnull60_start_date": None,
                "delmaxnull60_end_date": None,
                "delmaxnull60_cnt": None,
            }
        cnt = sum(day_cnt_dict.values())
        delmaxnull30_start_date = (
            max_continus_info["min_date"]
            if max_bad < 30
            else max_continus_info["max_bad_end_day"] + datetime.timedelta(days=1)
        )
        delmaxnull30_end_date = max_continus_info["max_date"]
        delmaxnull60_start_date = (
            max_continus_info["min_date"]
            if max_bad < 60
            else max_continus_info["max_bad_end_day"] + datetime.timedelta(days=1)
        )
        delmaxnull60_end_date = max_continus_info["max_date"]
        delmaxnull30_cnt = (
            cnt
            if max_bad < 30
            else sum(
                [v for k, v in day_cnt_dict.items() if k >= delmaxnull30_start_date]
            )
        )
        delmaxnull60_cnt = (
            cnt
            if max_bad < 60
            else sum(
                [v for k, v in day_cnt_dict.items() if k >= delmaxnull60_start_date]
            )
        )
        return {
            "start_date": max_continus_info["min_date"],
            "end_date": max_continus_info["max_date"],
            "cnt": cnt,
            "delmaxnull30_start_date": delmaxnull30_start_date,
            "delmaxnull30_end_date": delmaxnull30_end_date,
            "delmaxnull30_cnt": delmaxnull30_cnt,
            "delmaxnull60_start_date": delmaxnull60_start_date,
            "delmaxnull60_end_date": delmaxnull60_end_date,
            "delmaxnull60_cnt": delmaxnull60_cnt,
        }

    ##这个模块用来计算day(最大连续)特征模块所需要的一些信息
    def cal_max_continus(self, data_dic):
        if not data_dic:
            return {
                "good_list": None,
                "bad_list": None,
                "min_date": None,
                "max_date": None,
                "max_good": None,
                "max_bad": None,
                "max_good_start_day": None,
                "max_good_end_day": None,
                "max_bad_start_day": None,
                "max_bad_end_day": None,
            }
        bad = 0
        good = 1
        good_list = []
        bad_list = []
        min_date = min(data_dic)
        max_date = max(data_dic)
        max_good = 1
        max_good_end_day = min_date
        max_bad = 0
        max_bad_end_day = min_date + datetime.timedelta(days=-1)
        for cur_day in self.date_range(min_date, max_date):
            pre_day = cur_day + datetime.timedelta(days=-1)
            if cur_day in data_dic and pre_day in data_dic:
                good += 1
            elif cur_day not in data_dic and pre_day not in data_dic:
                bad += 1
            elif cur_day not in data_dic and pre_day in data_dic:
                bad = 1
                good_list.append(good)
                if good >= max_good:
                    max_good = good
                    max_good_end_day = pre_day
            else:
                good = 1
                if bad != 0:
                    bad_list.append(bad)
                if bad >= max_bad:
                    max_bad = bad
                    max_bad_end_day = pre_day
        good_list.append(good)
        if good >= max_good:
            max_good = good
            max_good_end_day = pre_day
        max_good_start_day = max_good_end_day + datetime.timedelta(days=-(max_good - 1))
        max_bad_start_day = max_bad_end_day + datetime.timedelta(days=-(max_bad - 1))
        return {
            "good_list": good_list,
            "bad_list": bad_list,
            "min_date": min_date,
            "max_date": max_date,
            "max_good": max_good,
            "max_bad": max_bad,
            "max_good_start_day": max_good_start_day,
            "max_good_end_day": max_good_end_day,
            "max_bad_start_day": max_bad_start_day,
            "max_bad_end_day": max_bad_end_day,
        }

    ####下面是用到次数比较多的逻辑，写的一些help函数

    ##这是使用递归求排列组合的一个函数
    def combination(self, data_list):
        path = []
        rel = []

        def backtrack(i):
            if i == len(data_list):
                rel.append(self.gen_feature_name(path))
                return
            for j in data_list[i]:
                path.append(j)
                backtrack(i + 1)
                path.pop()

        backtrack(0)
        return rel

    ##这是定位根目录的函数
    def get_root_dir(self, path):
        path_list = path.split(os.path.sep)
        index = path_list.index("featurelib")
        return os.path.sep.join(path_list[: index + 1])

    ##这是将字符转为datetime的函数
    def trans_str_to_time(self, str_time):
        return datetime.datetime.strptime(str_time, "%Y-%m-%d %H:%M:%S")

    ##这是一个衍生特征名字的函数
    def gen_feature_name(self, name_list):
        return "_".join([str(x) for x in name_list if x != "all" and x != ""])

    ##这是一个计算分位数的函数
    def cal_quantile(self, data, default=(50, 90)):
        if not data:
            return None, None
        return (np.percentile(data, x) for x in default)

    ##这是一个计算两数之差的函数
    def cal_two_difference(self, num1, num2):
        if num1 == None or num2 == None:
            return None
        return num1 - num2

    ##这是一个计算两数之商的函数
    def cal_two_division(self, num1, num2):
        if num1 == None or (num2 == None or num2 == 0):
            return None
        return 100 * num1 / num2

    ##这是一个计算两个日期差值的函数
    def cal_two_date_diff(self, date1, date2):
        if date1 == None or date2 == None:
            return None
        return (date1 - date2).days

    ##这是一个构造日期迭代器的函数
    def date_range(self, start_date, end_date):
        cur_date = start_date + datetime.timedelta(days=-1)
        for i in range((end_date - start_date).days + 1):
            cur_date += datetime.timedelta(days=1)
            yield cur_date

    ##这是一个计算最大，最小，均值，方差的函数
    def cal_max_min_avg_std(self, data):
        if not data:
            return None, None, None, None
        max_value = max(data)
        min_value = min(data)
        avg_value = sum(data) / len(data)
        std_value = (
            None
            if len(data) == 1
            else sum([(x - avg_value) ** 2 for x in data]) / (len(data) - 1)
        )
        return max_value, min_value, avg_value, std_value

    ##这是一个计算联系密度的函数
    def cal_density(self, data_list):
        if not data_list:
            return None
        data_list.sort()
        result = pow(data_list[0], 2)
        for i in range(1, len(data_list)):
            result += pow((data_list[i] - data_list[i - 1]), 2)
        density = pow(result, 0.5)
        return density

    ##这是一个计算value值最大时的key的函数
    def cal_max_value(self, data):
        if not data:
            return None, None
        max_value = max(data.values())
        max_value_index = [k for k, v in data.items() if v == max_value]
        return min(max_value_index), max_value

    ##这是一个去掉strdata最后一位并加上new的函数
    def pop_append(self, strdata, new):
        return strdata[:-1] + new

    ##这是一个计算data中非0的个数的函数
    def cal_nozero_num(self, data):
        if data == None:
            return None
        return sum([1 if x != 0 else 0 for x in data])
